package FiatShamirProofs;

import ArithmeticObjects.*;
import ArithUtils.*;
import CryptoPrim.PRG;
import CryptoPrim.RandomOracle;
import Utils.*;


/**
 * The ProofOASOC class represents a non-interactive ZKP of
 * Fiat-Shamir's Proof of a Shuffle of Commitments.
 * Allows the mix-server to show in a pre-computation phase that
 * it knows how to open a commitment to a permutation.
 * Has similar functionality to ProofOASOC, so the last inherits from it.
 * 
 * @author Itamar Carmel, amirzell
 */
public class ProofOASOC extends Proof{
	
	protected Array<VerfGroup.VElement> u; // permutation commitment
	
	// Fields common to ProofOASOC and ProofOAS
	protected BigNumber[] e; // requested at ProofOAS
	protected Array<BTField<BigNumber>.BTElement> kE; // requested at ProofOAS
	protected byte[] seed; // if instance of ProofOAS - computed at ProofOAS
	protected BigNumber v; // requested at ProofOAS
	protected Array<VerfGroup.VElement> h;
	
	/**
	 * Constructs a Proof of a Shuffle of Commitments by it's parameters.
	 * 
	 * @param iop - inputs of proof
	 */
	public ProofOASOC(RandomOracle ROSeed, RandomOracle ROChallenge,
			byte[] rho, int N0, int ne, int nr, int nv, PRG prg, VerfGroup Gq,
			Array<VerfGroup.VElement> u, ByteTree tau, ByteTree sigma, Array<VerfGroup.VElement> h) {
		super(ROSeed, ROChallenge, rho, N0, ne, nr, nv, prg, Gq, tau, sigma);
		this.u = u;
		this.h = h;
		this.e = null;
		this.kE = null;
		this.seed = null;
		this.v = null;
	}

	public boolean verifyProof(){

		////////////
		// STEP 1 //
		////////////
		
		// Interpret Objects
		VerfGroup.VElement g = Gq.getGenerator(); // assign generator element
		BigNumber q = Gq.getOrder(); // assign order element
		BTField<BigNumber> Zq = staticTypes.getNumberField(q); // initiate field
		
		// parse BTs
		VerfGroup.VElement APrime = Gq.getElement(tau.getTheNChild(1));
		VerfGroup.VElement CPrime = Gq.getElement(tau.getTheNChild(3));
		VerfGroup.VElement DPrime = Gq.getElement(tau.getTheNChild(4));		
		Array<VerfGroup.VElement> B = Gq.getArrayOfElementsFromBT(tau.getTheNChild(0));		
		Array<VerfGroup.VElement> BPrime = Gq.getArrayOfElementsFromBT(tau.getTheNChild(2));		
		BTField<BigNumber>.BTElement kA = Zq.getElement(sigma.getTheNChild(0));		
		BTField<BigNumber>.BTElement kC = Zq.getElement(sigma.getTheNChild(2));
		BTField<BigNumber>.BTElement kD = Zq.getElement(sigma.getTheNChild(3));
		Array<BTField<BigNumber>.BTElement> kB = Zq.getElementArray(sigma.getTheNChild(1));
		kE = Zq.getElementArray(sigma.getTheNChild(4)); // Sets kE field

		////////////
		// STEP 2 //
		////////////
		
		ByteTree bt;
		
		// Compute seed
		if (seed == null){ // Case seed is not computed - not an instance of ProofOAS
			bt = ByteTree.generateEmptyNode();
			// set bt
			bt.addMultChildren(g.toByteTree(),
					Gq.toByteTree(h),
					Gq.toByteTree(u));
			// compute seed by bt
			seed = computeSeed(bt);
		}
		
		////////////
		// STEP 3 //
		////////////
		
		// Compute A
		e = deriveRandomBigNumbers(seed); // Sets e field
		VerfGroup.VElement A = Gq.pow(u.get(0),e[0]);
		// multiply and exponent
		for (int i=1; i<N; i++)
			A = Gq.mult(A,Gq.pow(u.get(i),e[i]));
		
		////////////
		// STEP 4 //
		////////////
		
		// Compute v (challenge)
		bt = ByteTree.generateEmptyNode();
		// set BT
		bt.addMultChildren(ByteTree.generateNewLeaf(seed),tau);
		v = computeChallenge(bt); // Sets v field
		
		////////////
		// STEP 5 //
		////////////
		
		// Compute C
		VerfGroup.VElement CDividend = u.get(0);
		// multiply elements
		for (int i=1; i<N; i++)
			CDividend = Gq.mult(CDividend,u.get(i));
		VerfGroup.VElement CDivisor = h.get(0);
		// multiply elements
		for (int i=1; i<N; i++)
			CDivisor = Gq.mult(CDivisor,h.get(i));
		VerfGroup.VElement C = Gq.mult(CDividend,Gq.inverse(CDivisor));
		
		// Compute D
		VerfGroup.VElement DDividend = B.get(N - 1);
		BigNumber DExp = e[0];
		// multiply elements
		for (int i=1; i<N; i++)
			DExp = DExp.multiply(e[i]);
		VerfGroup.VElement DDivisor = Gq.pow(h.get(0),DExp);
		VerfGroup.VElement D = Gq.rightAdj(DDividend,DDivisor);
		
		// Verification
		VerfGroup.VElement left;
		VerfGroup.VElement right;
		
		// Verify #1 equation [A^v*A' = g^ka*PI(h[i]^ke[i])]
		left = Gq.mult(Gq.pow(A,v),APrime);
		VerfGroup.VElement hPI = Gq.pow(h.get(0),kE.get(0).getValue());
		// multiply and exponent
		for (int i=1; i<N; i++)
			hPI = Gq.mult(hPI,Gq.pow(h.get(i),kE.get(i).getValue()));
		right = Gq.mult(Gq.pow(g,kA.getValue()),hPI);
		// check equality
		if (!Gq.ElementIsEqual(left,right))
			return false;
		
        // Verify #2 equation [B[i]^v*B'[i] = g^kb[i]*B[i-1]^ke[i] for i = 0,..,N-1]
		left = Gq.mult(Gq.pow(B.get(0),v),BPrime.get(0));
		// Uses h[0] instead of B[-1]
		right = Gq.mult(Gq.pow(g,kB.get(0).getValue()),Gq.pow(h.get(0),kE.get(0).getValue()));
		// check equality
		if (!Gq.ElementIsEqual(left,right))
			return false;
		// multiply and exponent
		for (int i=1; i<N; i++){
			left = Gq.mult(Gq.pow(B.get(i),v),BPrime.get(i));
			right = Gq.mult(Gq.pow(g,kB.get(i).getValue()),Gq.pow(B.get(i-1),kE.get(i).getValue()));
			// check equality
			if (!Gq.ElementIsEqual(left,right))
				return false;
		}
		
        // Verify #3 equation [C^v*C' = g^kc]
		left = Gq.mult(Gq.pow(C,v),CPrime);
		right = Gq.pow(g,kC.getValue());
		// check equality
		if (!Gq.ElementIsEqual(left,right))
			return false;
		
        // Verify #4 equation [D^v*D' = g^kd]
		left = Gq.mult(Gq.pow(D,v),DPrime);
		right = Gq.pow(g,kD.getValue());
		// check equality
		if (!Gq.ElementIsEqual(left,right))
			return false;
		
		// Case all above equations verified - Proof verified
		return true;
	}
}
